// AppdicUpgrade.cpp
// AppdicUpgrade
// Application Dictionary (APPDIC) Upgrader / Used to patch/upgrade existing APPDIC data
// Copyright (C) 2003, 2004, 2005 eXegeSys, Inc.
// Copyright (C) 2008 Bruce A. James
//
// This program is part of the Protea Project.
//
// The Protea Project is free software; you can redistribute it and/or modify it 
// under the terms of the GNU General Public License as published by the 
// Free Software Foundation, either version 3 of the License, or (at your 
// option) any later version.
//
// The Protea Project is distributed in the hope that it will be useful, but 
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
// for more details.
// 
// You should have received a copy of the GNU General Public License along 
// with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// Please send correspondence to:
//
// theproteaproject@gmail.com
//

#include "../SDK/ToolsInterface.h"
#include "../OSDep/utf8.h"

#include <xercesc/dom/DOM.hpp>
#include <xercesc/parsers/XercesDOMParser.hpp>
#include <xercesc/sax2/DefaultHandler.hpp>
#include <xercesc/sax2/XMLReaderFactory.hpp>
#include <xercesc/sax2/Attributes.hpp>
#include <xercesc/framework/XMLFormatter.hpp>

#include "../defns.h"
using namespace ProteaApp1;

const long PADDING = 10;
char* filename = "results.xml";

XCManualEvent wait1( true );
XCManualEvent wait2( false );

int app_number = -1;
bool parse_error = false;
XCToolsError* exception_value = NULL;

XCFileTransport outputfile( NULL );
int last_tag = -2;
bool buffer_output;

struct StructureField {
	char name[ 33 ];
	int number;
	int type;
	int length;
	bool primary;
	char defaultval[ 255 ];

	int offset;
	StructureField* next;
};



struct RecordData {
	RecordData* prev;
	RecordData* next;
	char data[];
};




class parser_class : public XERCES_CPP_NAMESPACE::DefaultHandler {
public:
	XCManualEvent* start_wait;
	XCManualEvent* end_wait;

	char buffername[ 255 ];
	int buffernumber;
	char field_data[ 255 ];
	int record_size;

	StructureField* structures;
	StructureField* last_structure;

	StructureField* current;
	RecordData* records;
	RecordData* last_record;

	bool InStructure;
	bool skip_buffer;

	void ClearData() {
		// Delete the field structure records
		StructureField* stemp = structures;
		while (stemp) {
			StructureField* next = stemp->next;
			delete stemp;
			stemp = next;
		};
		structures = NULL;
		last_structure = NULL;

		// Delete all the records
		RecordData* rtemp = records;
		while (rtemp) {
			RecordData* next = rtemp->next;
			delete rtemp;
			rtemp = next;
		};
		records = NULL;
		last_record = NULL;
	};

	// Virtual abstract functions
	virtual void StructureFinished() = 0;
	virtual void BufferStarted() = 0;
	virtual void BufferFinished() = 0;

	// Virtual functions
	virtual void HandleApplication( bool start, const XERCES_CPP_NAMESPACE::Attributes* atts )
	{
		// Wait forever to start
		do {
		} while (!parse_error && start_wait->Wait( 1 ) == XCWAIT_TIMEOUT);

		if (start) {
			InStructure = false;
			structures = NULL;
			last_structure = NULL;
			records = NULL;
			last_record = NULL;

			// Obtain the application number
			int my_app = -1;
			int len = atts->getLength();
			for (int i=0; i<len; i++) {
				UniTranslator name( atts->getLocalName( i ) );
				UniTranslator value( atts->getValue( i ) );

				if (strcmp( name, "Number" )==0) {
					my_app = atoi( value );
				};
			};

			// We were not supplied an app_number
			if (my_app == -1) {
				throw XCToolsError( ERR_NO_APP_NUMBER, __LINE__, NULL, 0, 0, 0 );
			} else {
				if (app_number == -1)
					app_number = my_app;
				else if (app_number != my_app) {
					throw XCToolsError( ERR_APP_NUMBER_MISMATCH, __LINE__, NULL, 0, 0, 0 );
				};
			};
		};

		// Clear the event
		start_wait->Reset();

		// Signal the other thread
		end_wait->Raise();
	};

	virtual void HandleBuffer( bool start, const XERCES_CPP_NAMESPACE::Attributes* atts )
	{
		if (start) {
			// Wait forever to start
			do {
			} while (!parse_error && start_wait->Wait( 1 ) == XCWAIT_TIMEOUT);

			// We are done with this buffer
			ClearData();

			// Grab the buffer name
			int len = atts->getLength();
			for (int i=0; i<len; i++) {
				UniTranslator name( atts->getLocalName( i ) );
				UniTranslator value( atts->getValue( i ) );

				if (strcmp( name, "Number" )==0) {
					buffernumber = atoi( value );
				} else if (strcmp( name, "Name" ) ==0) {
					strcpy( buffername, value );
				} else
					printf( "INVALID XML!!\n" );
			};

			// Signal
			BufferStarted();
		} else {
			// Signal
			BufferFinished();

			// Clear the event
			start_wait->Reset();

			// Signal the other thread
			end_wait->Raise();
		};
	};

	virtual void HandleStructure( bool start, const XERCES_CPP_NAMESPACE::Attributes* atts )
	{
		InStructure = start;
		if (InStructure) {
			record_size = 0;
			structures = NULL;
			last_structure = NULL;
		} else {
			StructureFinished();

			// Prepare for reading records
			records = NULL;
			last_record = NULL;
		};
	};

	virtual void HandleRecord( bool start, const XERCES_CPP_NAMESPACE::Attributes* atts )
	{
		if (start) {
			// Allocate the new record
			int allocsize = sizeof( RecordData ) + record_size;
			RecordData* temp = (RecordData*)new char[ allocsize ];
			memset( temp, 0, allocsize );

			// Setup the pointers
			if (!last_record) {
				records = last_record = temp;
				last_record->prev = NULL;
			} else {
				last_record->next = temp;
				last_record->next->prev = last_record;
				last_record = last_record->next;
			};

			current = structures;
		};
	};

	virtual void HandleField( bool start, const XERCES_CPP_NAMESPACE::Attributes* atts )
	{
		if (InStructure) {
			if (start) {
				StructureField* field = new StructureField;
				memset( field, 0, sizeof( StructureField ) );

				if (!last_structure)
					last_structure = structures = field;
				else {
					field->offset = record_size;
					last_structure->next = field;
					last_structure = field;
				};

				// Fill in the structure information
				int len = atts->getLength();
				for (int i=0; i<len; i++) {
					UniTranslator name( atts->getLocalName( i ) );
					UniTranslator value( atts->getValue( i ) );

					if (strcmp( name, "Name" )==0)
						strcpy( field->name, value );
					else if (strcmp( name, "Number" )==0)
						field->number = atoi( value );
					else if (strcmp( name, "Type" )==0)
						field->type = atoi( value );
					else if (strcmp( name, "Length" )==0)
						field->length = atoi( value );
					else if (strcmp( name, "Default" )==0)
						strcpy( field->defaultval, value );
					else if (strcmp( name, "Primary" )==0)
						field->primary = atoi( value );
				};

				record_size += field->length + PADDING;
			};
		} else {
			if (!start) {
				// We are in a record, so copy over field data
				strcpy( &last_record->data[ current->offset ], field_data );

				// Move on to the next field
				current = current->next;
			};
		};
	};

	// SAX Parser
	void startElement( const XMLCh* const uri, const XMLCh* const localname, const XMLCh* const qname, const XERCES_CPP_NAMESPACE::Attributes& atts )
	{
		if (!parse_error) {
			UniTranslator name( localname );

			*field_data = 0;
			if (strcmp( name, "Application" )==0) {
				HandleApplication( true, &atts );
			} else if (strcmp( name, "Buffer" )==0) {
				HandleBuffer( true, &atts );
			} else if (strcmp( name, "STRUCTURE" )==0) {
				HandleStructure( true, &atts );
			} else if (strcmp( name, "RECORD" )==0) {
				HandleRecord( true, &atts );
			} else if (strcmp( name, "Field" )==0) {
				HandleField( true, &atts );
			} else if (strcmp( name, "Components" )==0 || strcmp( name, "Component" )==0) {
				// NOP
			} else
				printf( "Invalid tag: '%s'\n", name.UTF8() );
		};
	};


	void characters( const XMLCh* const chars, const unsigned int length )
	{
		if (!skip_buffer && !parse_error) {
			UniTranslator string( chars );
			strcat( field_data, string );
		};
	};


	void endElement( const XMLCh* const uri, const XMLCh* const localname, const XMLCh* const qname )
	{
		if (!parse_error) {
			UniTranslator name( localname );

			if (strcmp( name, "Application" )==0) {
				HandleApplication( false, NULL );
			} else if (strcmp( name, "Buffer" )==0) {
				HandleBuffer( false, NULL );
			} else if (strcmp( name, "STRUCTURE" )==0) {
				HandleStructure( false, NULL );
			} else if (strcmp( name, "RECORD" )==0) {
				HandleRecord( false, NULL );
			} else if (strcmp( name, "Field" )==0) {
				HandleField( false, NULL );
			} else if (strcmp( name, "Components" )==0 || strcmp( name, "Component" )==0) {
				// NOP
			} else
				printf( "Invalid tag: '%s'\n", name.UTF8() );
		};
	};


	bool MatchPrimary( RecordData* match1, RecordData* match2 ) {
		StructureField* temp = structures;
		while (temp) {
			// Only match primary fields
			if (temp->primary) {
				// We found a primary field, compare the data
				if (strcmp( &match1->data[ temp->offset ], &match2->data[ temp->offset ] )!=0)
					return false;
			};

			temp = temp->next;
		};

		// If we get here, all primary keys matched
		return true;
	};

	bool MatchFields( RecordData* match1, RecordData* match2 ) {
		StructureField* temp = structures;
		while (temp) {
			if (strcmp( &match1->data[ temp->offset ], &match2->data[ temp->offset ] )!=0)
				return false;
			temp = temp->next;
		};

		// If we get here, all data matched
		return true;
	};

	void EliminateRecord( RecordData* data ) {
		if (!data->prev) {
			// First one in the list
			records = data->next;
			if (records)
				records->prev = NULL;
		} else {
			// Reset our previous pointer
			data->prev->next = data->next;
		};

		if (!data->next) {
			// Last one in the list
			last_record = data->prev;
			if (last_record)
				last_record->next = NULL;
		} else {
			// Reset our next previous pointer
			data->next->prev = data->prev;
		};
		delete [] (char*)data;
	};


	void OutputTypeTag( int type, bool start=true ) {
		if (!start) {
			switch( type ) {
				case -1:
					outputfile.Send( "\t\t</Deleted>\n" );
					break;
				case 0:
					outputfile.Send( "\t\t</Changed>\n" );
					break;
				case 1:
					outputfile.Send( "\t\t</Added>\n" );
					break;
			};
		} else if (type != last_tag) {
			OutputTypeTag( last_tag, false );
			switch( type ) {
				case -1:
					outputfile.Send( "\t\t<Deleted>\n" );
					break;
				case 0:
					outputfile.Send( "\t\t<Changed>\n" );
					break;
				case 1:
					outputfile.Send( "\t\t<Added>\n" );
					break;
			};
			last_tag = type;
		};
	};

	void OutputFieldTag( StructureField* field, const char* data, const char* orig = NULL ) {
		char buffer[ 1024 ];
		char* temp = buffer;

		temp += sprintf( temp, "\t\t\t\t<Field Name='%s' Number='%d'>", field->name, field->number );
		if (!orig) {
			temp += sprintf( temp, "%s", data );
		} else {
			temp += sprintf( temp, "\n\t\t\t\t\t<From>%s</From>\n\t\t\t\t\t<To>%s</To>\n\t\t\t\t",
				data,
				orig );
		};
		temp += sprintf( temp, "</Field>\n" );

		outputfile.Send( buffer );
	};

	void OutputBufferTag( bool start ) {
		char buffer[ 255 ];
		if (start) {
			if (!buffer_output) {
				sprintf( buffer, "\t<Buffer Name='%s' Number='%d'>\n", buffername, buffernumber );
				outputfile.Send( buffer );
				last_tag = -2;
				buffer_output = true;
			};
		} else {
			OutputTypeTag( -2, true );
			if (buffer_output) {
				outputfile.Send( "\t</Buffer>\n" );
				buffer_output = false;
			};
		};
	};

	void OutputInformation( RecordData* orig, RecordData* changed ) {
		StructureField* s = structures;

		OutputBufferTag( true );

		if (orig && !changed) {
			OutputTypeTag( -1, true );
			outputfile.Send( "\t\t\t<Record>\n" );

			// Output the primary keys
			while (s) {
				if (s->primary)
					OutputFieldTag( s, &orig->data[ s->offset ] );
				s = s->next;
			};
		} else if (!orig && changed) {
			OutputTypeTag( +1 );
			outputfile.Send( "\t\t\t<Record>\n" );
			while (s) {
				OutputFieldTag( s, &changed->data[ s->offset ] );
				s = s->next;
			};
		} else {
			OutputTypeTag( 0 );
			outputfile.Send( "\t\t\t<Record>\n" );
			while (s) {
				const char* s1 = &orig->data[ s->offset ];
				const char* s2 = &changed->data[ s->offset ];
				if (s->primary)
					OutputFieldTag( s, s1 );
				else if (strcmp( s1, s2 )!=0)
					OutputFieldTag( s, s1, s2 );
				s = s->next;
			};
		};
		outputfile.Send( "\t\t\t</Record>\n" );
	};
};


class original_parser_class : public parser_class {
public:
	original_parser_class() {
		start_wait = &wait1;
		end_wait = &wait2;
	};

	void BufferStarted() {
		printf( "Loading %s\n", buffername );
	};

	void BufferFinished() {
		// Do nothing
	};

	void StructureFinished() {
		// We do not want to skip this buffer
		skip_buffer = false;
	};
};

class changed_parser_class : public parser_class {
public:
	changed_parser_class( original_parser_class* parent ) {
		original = parent;
		start_wait = parent->end_wait;
		end_wait = parent->start_wait;
	};

	void BufferStarted() {
		// Do nothing
	};

	void BufferFinished() {
		RecordData* temp = records;
		while (temp) {
			RecordData* ToDelete = NULL;

			// Search and see if we can find a match for this record in the original
			RecordData* odata = original->records;
			while (odata && !MatchPrimary( temp, odata ))
				odata = odata->next;

			// We found a match on primary keys
			if (odata) {
				// Now check data
				if (!MatchFields( odata, temp )) {
					// They are not a match, so output a change record
					OutputInformation( odata, temp );
				};

				// Either way, delete the record, it is taken care of
				original->EliminateRecord( odata );
				ToDelete = temp;
			};

			// Move on to the next record
			temp = temp->next;

			// Delete anything we need to
			if (ToDelete)
				EliminateRecord( ToDelete );
		};

		// At this point, the original APPDIC will contain records that need to be zapped
		// and the changed APPDIC will contain records that need to be added.  Changed
		// records will have already been written out and removed from the list
		temp = records;
		while (temp) {
			OutputInformation( NULL, temp );
			temp = temp->next;
		};

		temp = original->records;
		while (temp) {
			OutputInformation( temp, NULL );
			temp = temp->next;
		};

		// Close the buffer tags
		OutputBufferTag( false );
	};

	void StructureFinished() {
		// Compare the two structures and make sure that everything is identical
		StructureField* a = original->structures;
		StructureField* b = structures;

		skip_buffer = false;
		while (a && !skip_buffer) {
			if (!b) {
				printf( "Field existed in old APPDIC that no longer exists\n" );
				skip_buffer = true;
			} else if (a->number != b->number) {
				printf( "Field mismatch between old and new APPDIC\n" );
				skip_buffer = true;
			} else {
				a = a->next;
				b = b->next;
			};
		};
		if (b) {
			printf( "Field exists in new APPDIC that doesn't exist in old APPDIC\n" );
			skip_buffer = true;
		};
	};

	original_parser_class* original;
};

struct thread_input {
	original_parser_class* orig_parser;
	changed_parser_class* change_parser;
	const char* in_filename;
	const char* out_filename;
};


THREAD_PROCEDURE OriginalThread( void* param ) {
	thread_input* input = (thread_input*)param;
	original_parser_class* orig_parser = input->orig_parser;

	// Create the parser
	XERCES_CPP_NAMESPACE::SAX2XMLReader* parser =
		XERCES_CPP_NAMESPACE::XMLReaderFactory::createXMLReader();
	parser->setContentHandler( orig_parser );
	parser->setErrorHandler( orig_parser );

	// Start the parse and wait for finish
	try {
		parser->parse( input->in_filename );
	} catch( XCToolsError& error ) {
		exception_value = error.Copy();
		parse_error = true;
	} catch( XERCES_CPP_NAMESPACE::SAXException & sax ) {
		UniTranslator error( sax.getMessage() );
		exception_value = new XCToolsError( ERR_XML_PARSE, __LINE__, NULL, 0, 0, 1, error.UTF8() );
		parse_error = true;
	} catch( ... ) {
		exception_value = new XCToolsError( -1, __LINE__, NULL, 0, 0, 0 );
		parse_error = true;
	};

	// Exit the thread
	return 0;
};


THREAD_PROCEDURE ChangedThread( void* param ) {
	thread_input* input = (thread_input*)param;
	changed_parser_class* change_parser = input->change_parser;

	// Create the parser
	XERCES_CPP_NAMESPACE::SAX2XMLReader* parser =
		XERCES_CPP_NAMESPACE::XMLReaderFactory::createXMLReader();
	parser->setContentHandler( change_parser );
	parser->setErrorHandler( change_parser );

	// Start the parse and wait for finish
	try {
		parser->parse( input->out_filename );
	} catch( XCToolsError& error ) {
		exception_value = error.Copy();
		parse_error = true;
	} catch( XERCES_CPP_NAMESPACE::SAXException & sax ) {
		UniTranslator error( sax.getMessage() );
		exception_value = new XCToolsError( ERR_XML_PARSE, __LINE__, NULL, 0, 0, 1, error.UTF8() );
		parse_error = true;
	} catch( ... ) {
		exception_value = new XCToolsError( -1, __LINE__, NULL, 0, 0, 0 );
		parse_error = true;
	};

	// Exit the thread
	return 0;
};


bool CreateChangeList( const char* original, const char* changed ) {
	// Create the input records
	thread_input input;
	input.orig_parser = new original_parser_class();
	input.change_parser = new changed_parser_class( input.orig_parser );
	input.in_filename = original;
	input.out_filename = changed;

	// Create the output file
	outputfile.Open( filename, write_only, create_always );
	outputfile.Send( "<?xml version=\"1.0\"?>\n<Upgrades>\n" );

	// Start the two worker threads
	THREAD_HANDLE o_thread = XCCreateThread( OriginalThread, &input );
	THREAD_HANDLE c_thread = XCCreateThread( ChangedThread, &input );

	// Wait for the threads to finish
	XCJoinThread( o_thread );
	XCJoinThread( c_thread );

	// Close the output file
	outputfile.Send( "</Upgrades>\n" );
	outputfile.Close();

	delete input.orig_parser;
	delete input.change_parser;

	// If an exception occurred, rethrow in the main thread.
	if (exception_value)
		throw *exception_value;

	return !parse_error;
};





/////////
//
// Execution of changes
//
/////////
class XCExecute : public XCAppInterface, public XERCES_CPP_NAMESPACE::DefaultHandler {
public:
	XCExecute() {
		compare = NULL;
		buffer = NULL;
	};

	void Run() {
		// Create a database transaction
		query = new XCSQLGen( this );
		dbtxn = GetDatabase()->CreateTxn();

		// Parse the results file
		XERCES_CPP_NAMESPACE::SAX2XMLReader* parser = XERCES_CPP_NAMESPACE::XMLReaderFactory::createXMLReader();
		parser->setContentHandler( this );
		parser->setErrorHandler( this );

		try {
			parser->parse( filename );
		} catch( XCToolsException& err ) {
			// Rollback the transaction
			dbtxn->EndTxn( false );
			delete query;
			throw;
		};

		// Close the database transaction
		dbtxn->EndTxn( true );
		delete query;
	};

protected:
	char field_data[ 255 ];
	XCBuffer* buffer;
	XCBuffer* compare;
	XCField* current;
	XCTxn* dbtxn;
	XCSQLGen* query;
	int mode;
	bool change;

	void HandleBuffer( bool start, const XERCES_CPP_NAMESPACE::Attributes* attrib ) {
		if (start) {
			int len = attrib->getLength();
			int buffernumber = 0;

			for (int i=0; i<len; i++) {
				UniTranslator name( attrib->getLocalName( i ) );
				UniTranslator value( attrib->getValue( i ) );

				if (strcmp( name, "Number" )==0)
					buffernumber = atoi( value );
			};

			// Create our work buffer
			buffer = CreateBuffer( buffernumber, true );

			// Set our default mode
			mode = 255;
		} else {
			buffer->Release();
		};
	};


	void HandleRecord( bool start, const XERCES_CPP_NAMESPACE::Attributes* attrib )
	{
		if (start) {
			if (compare)
				compare->LoadDefaults();
			buffer->LoadDefaults();
		} else {
			switch( mode ) {
				case 0:
					printf( "BUFFER:\n" );
					buffer->DebugDump();
					printf( "COMPARE:\n" );
					compare->DebugDump();
					query->MakeFull( SQL_STMT_UPDATE, buffer, compare );
					break;
				case 1:
					query->MakeFull( SQL_STMT_INSERT, buffer );
					break;
				case -1:
					query->MakeFull( SQL_STMT_DELETE, buffer );
					break;
			};
			dbtxn->ExecuteSQL( query, SQL_NO_RESULT );
		};
	};


	void HandleField( bool start, const XERCES_CPP_NAMESPACE::Attributes* attrib )
	{
		if (start) {
			int len = attrib->getLength();
			int number = 0;

			for (int i=0; i<len; i++) {
				UniTranslator name( attrib->getLocalName( i ) );
				UniTranslator value( attrib->getValue( i ) );

				if (strcmp( name, "Number" )==0)
					number = atoi( value );
			};

			current = buffer->GetField( number );
			change = false;
		} else {
			// If we have a field value, set it
			if (*field_data && !change) {
				current->SetString( field_data );
				if (compare)
					compare->GetField( current->GetFieldNumber() )->SetString( field_data );
			};
			current = NULL;
		};
	};



	void HandleFrom( bool start, const XERCES_CPP_NAMESPACE::Attributes* attrib )
	{
		if (!start) {
			if (*field_data)
				compare->GetField( current->GetFieldNumber() )->SetString( field_data );
			change = true;
		};
	};


	void HandleTo( bool start, const XERCES_CPP_NAMESPACE::Attributes* attrib )
	{
		if (!start) {
			if (*field_data)
				current->SetString( field_data );
			change = true;
		};
	};


	// SAX Parser
	void startElement( const XMLCh* const uri, const XMLCh* const localname, const XMLCh* const qname, const XERCES_CPP_NAMESPACE::Attributes& atts )
	{
		UniTranslator name( localname );

		*field_data = 0;
		if (strcmp( name, "Buffer" )==0) {
			HandleBuffer( true, &atts );
		} else if (strcmp( name, "Added" )==0) {
			mode = 1;
		} else if (strcmp( name, "Deleted" )==0) {
			mode = -1;
		} else if (strcmp( name, "Changed" )==0) {
			compare = buffer->Copy();
			mode = 0;
		} else if (strcmp( name, "Record" )==0) {
			HandleRecord( true, &atts );
		} else if (strcmp( name, "Field" )==0) {
			HandleField( true, &atts );
		} else if (strcmp( name, "From" )==0) {
			HandleFrom( true, &atts );
		} else if (strcmp( name, "To" )==0) {
			HandleTo( true, &atts );
		};
	};

	void characters( const XMLCh* const chars, const unsigned int length )
	{
		UniTranslator string( chars );
		strcat( field_data, string );
	};

	void endElement( const XMLCh* const uri, const XMLCh* const localname, const XMLCh* const qname )
	{
		UniTranslator name( localname );
		if (strcmp( name, "Buffer" )==0) {
			HandleBuffer( false, NULL );
		} else if (strcmp( name, "Changed" )==0) {
			compare->Release();
			compare = NULL;
		} else if (strcmp( name, "Record" )==0) {
			HandleRecord( false, NULL );
		} else if (strcmp( name, "Field" )==0) {
			HandleField( false, NULL );
		} else if (strcmp( name, "From" )==0) {
			HandleFrom( false, NULL );
		} else if (strcmp( name, "To" )==0) {
			HandleTo( false, NULL );
		};
		*field_data = 0;
	};
};


void ShowUsage() {
	printf( "One of the following flags may be used:\n" );
	printf( "  /a [original] [changed] Perform upgrade analysis only\n" );
	printf( "  /f [filename]           Perform upgrade using analysis file\n" );
	printf( "  /u [original] [changed] Perform upgrade analysis, and upgrade\n" );
	exit(1);
};


enum upgrade_modes {
	MODE_UNDEFINED = 0,
	MODE_UPGRADE = 2,
	MODE_ANALYSIS_ONLY = 1,
	MODE_UPGRADE_FROM_ANALYSIS = 3
};


int main( int argc, char* argv[] ) {
	int result = 0;
	upgrade_modes mode = MODE_UNDEFINED;
	char* original;
	char* changed;
	bool failed = false;

	Protea::ShowIntro( "AppdicUpgrade" );

	if (argc > 1 && !strcmp(argv[1],"-L"))
	{
		Protea::ShowLicense();
		return 0;
	};

	printf( "Protea APPDIC upgrader\n" );

	int count = 1;
	while (count < argc) {
		char* arg = argv[count];
		if (*arg == '/') {
			count++;
			switch( arg[1] ) {
				case 'a':
				case 'u':
					// Check that no other flags are specified
					if (mode != MODE_UNDEFINED)
						ShowUsage();

					// Ensure that we are supplied two filenames
					if (argc - count != 2)
						ShowUsage();

					// Store the filenames
					original = argv[ count++ ];
					changed = argv[ count++ ];

					// Set the correct mode
					mode = (arg[1] == 'a') ? MODE_ANALYSIS_ONLY : MODE_UPGRADE;
					break;

				case 'f':
					// Check that no other flags are specified
					if (mode != MODE_UNDEFINED)
						ShowUsage();

					// Ensure that we are supplied one filename
					if (argc - count != 1)
						ShowUsage();

					// Store the filenames
					filename = argv[ count++ ];

					mode = MODE_UPGRADE_FROM_ANALYSIS;
					break;

				default:
					ShowUsage();
			};
		} else
			ShowUsage();
	};

	// Parse the command line
	if (mode == MODE_UNDEFINED)
		ShowUsage();

	// Initialize Xerces
	Protea::Initialize();

	try {
		XCAppOptions opts;
		XCExecute execute;
		execute.GetOptions( opts );
		opts.no_transactions = true;
//		opts.debug_type_filter |= TRC_DATABASE + TRC_BUFFER + TRC_NOLABEL;
		opts.debug_type_filter = 0;
		opts.debug_level_cutoff[ TCL_DATABASE ] = TLV_LOW;
		opts.debug_level_cutoff[ TCL_BUFFER ] = TLV_HIGH;
		opts.debug_level_cutoff[ TCL_NOLABEL ] = TLV_HIGH;
		execute.SetOptions( opts );
		execute.Initialize( "appdic_upgrade", 1 );

		if (mode != MODE_UPGRADE_FROM_ANALYSIS) {
			// First thing, create the change list
			failed = !CreateChangeList( original, changed );
		};

		if (mode != MODE_ANALYSIS_ONLY && !failed) {
			// Second thing, reparse the file and make the appropriate changes
			execute.Run();
		};
	} catch( XCToolsError& err ) {
		err.OutputCallStack( NULL );
		result = err.GetErrorNumber();
		failed = true;
	} catch( ... ) {
		result = -1;
		failed = true;
	};

	// Terminate Xerces
	Protea::Uninitialize();

	return result;
};
